/* 
 * File:   Terrain.cpp
 * Author: Nick
 * 
 * Created on 13 April 2011, 9:54 PM
 */

#include "Terrain.h"
#include "SceneModel.h"
#include "TerrainRenderer.h"
#include "BlockRenderer.h"
#include <stddef.h>
#include <stdint.h>

Block::Block(Cell* parent) {
    this->parent = parent;
    this->terrain_def = 0;
    this->terrain_def_id = 0;
    this->next = 0;
    this->prev = 0;
    this->do_draw = true;
}

Block::~Block() {

}

bool Block::check_draw(){
    UNIT npos[3];
    for(int i = 0; i < 6;i++){
        this->get_neighbour_pos(npos,i);
        Block* b = this->parent->get_block(npos);
        if(b == null || b->terrain_def == 0){
            do_draw = true;
            return true;
        }
    }
    do_draw = false;
    return false;
}

byte Block::get_type() {
    return BLOCK_TYPE_BLOCK;
}

void Block::set_pos(UNIT* pos){
    this->pos[X] = pos[X];
    this->pos[Y] = pos[Y];
    this->pos[Z] = pos[Z];
}

bool Block::is_at(UNIT* pos) {
//    printf ("Cell::is_at():1: %d,%d,%d\n",pos[0],pos[1],pos[2]);
//    printf ("Cell::is_at():2: %d,%d,%d\n",this->pos[0],this->pos[1],this->pos[2]);
    return pos[X] == this->pos[X] && pos[Y] == this->pos[Y] && pos[Z] == this->pos[Z];
}

void Block::draw(){
    if(!do_draw) return;

    if(terrain_def == null){
        terrain_def = primary_model->get_terrain_def(terrain_def_id);
    }

    terrain_def->renderer->draw(this);
}

Cell::Cell(UNIT dep, Cell* parent) : Block(parent) {
    depth = dep;

    count = 0;
    state = 0;

    first = 0;
    last = 0;

    blocks = 0;

    for(int i = 0; i < 8; i++){
        neighbours[i] = 0;
    }


}

Cell::~Cell() {
    //delete any children the cell may have
    delete [] blocks;
    //delete any neighbours the block has
    //delete [] neighbours;
}

//TODO:
byte* Block::to_byte_array(){
    //Calculate array size
    int size = 0;
    size += UNIT_SIZE;       //Depth
    size += UNIT_SIZE*3*depth; //Coordinate
    size += 4;       //terrainID
    size += 4;       //Seed
    size += 1;       //format
    size += 4;       //layload_size

    byte data[size];

    data[0] = depth;
    //data[UNIT_SIZE] =
    return 0;

}

byte Cell::get_type() {
    return BLOCK_TYPE_CELL;
}

//Cell* Cell::merge_block(UNIT* pos, Block* block, UNIT depth) {

void Cell::merge_block(UNIT* pos, Block* block, UNIT depth) {
    if (block->get_type() == BLOCK_TYPE_BLOCK) {
        set_block(pos, block);
        return;
    }

    Cell* current_cell = get_cell(pos);

    if (current_cell == null) { // Cool it's not a cell or it's not existing, lets just set it.
        set_block(pos, block);
        return;
    } else { //Curses there is already a cell there, ok update it's content
        Cell* cell = dynamic_cast<Cell*> (block);

        if (cell == null) {
            //If it doesn't cast to a cell, just replace the current block with whatever the hell this shit is.
            set_block(pos, block);
            return;
        }

        cell = dynamic_cast<Cell*> (cell->first);
        if (depth == 0) {
            //Merge the contents of the existing cell with the new cell.
            while (cell != null) {
                current_cell->merge_block(cell->pos, cell, depth - 1);
                cell = dynamic_cast<Cell*> (cell->next);
            }
        } else {
            //Replace the contents of the existing cell with the contents from the new cell.
            while (cell != null) {
                current_cell->merge_block(cell->pos, cell, depth - 1);
                cell = dynamic_cast<Cell*> (cell->next);
            }
        }
    }

}

Cell* Cell::set_block(UNIT* pos, Block* block) {
//    printf ("Cell::set_block(): %d,%d,%d\n",pos[0],pos[1],pos[2]);
    
    BlockPos bp;
    if (!_normalise_position(&bp, pos)){
        return 0;
    }
    block->set_pos(bp.pos);
    
    Block* eb = bp.cell->_get_block(bp.pos);

//    printf ("Cell::set_block(): %d,%d,%d\n",pos[0],pos[1],pos[2]);
    if (eb != null) {
//        printf ("Cell::set_block():eb: %d,%d,%d\n",pos[0],pos[1],pos[2]);
        if (eb->next != null) {
            eb->next->prev = block;
            block->next = eb->next;
        } else {
            bp.cell->last = block;
        }
//        printf ("Cell::set_block():eb: %d,%d,%d\n",pos[0],pos[1],pos[2]);

        if (eb->prev != null) {
            eb->prev->next = block;
            block->prev = eb->prev;
        } else {
            bp.cell->first = block;
        }
//        printf ("Cell::set_block():eb: %d,%d,%d\n",pos[0],pos[1],pos[2]);
        block->parent = bp.cell;
//        printf ("Cell::set_block():eb: %d,%d,%d\n",pos[0],pos[1],pos[2]);

        if (block->get_type() == 1) {
            Cell* cell = dynamic_cast<Cell*> (block);
            if (cell) {
                _register_cell(pos, cell);
            }
        }

        delete eb; //Destory the old block
    } else { // It's a new block
//        printf ("Cell::set_block():else: %d,%d,%d\n",pos[0],pos[1],pos[2]);
        bp.cell->_register_child(bp.pos, block);
    }
//    printf ("Cell::set_block(): %d,%d,%d\n",pos[0],pos[1],pos[2]);
    if (blocks != null) {
        blocks[pos_to_index(block->pos)] = block;
    }
//    printf ("Cell::set_block(): %d,%d,%d\n",pos[0],pos[1],pos[2]);
    //delete bp;
//    printf ("Cell::set_block():end: %d,%d,%d\n",pos[0],pos[1],pos[2]);

    return 0;
}

void Cell::_init_hash() {
    blocks = new Block*[CELL_WIDTH * CELL_HEIGHT * CELL_DEPTH];

    //TODO: Make this better or write a better todo =P
    for(int i = CELL_WIDTH * CELL_HEIGHT * CELL_DEPTH -1; i >= 0 ; i-- ){
        blocks[i] = 0;
    }

    Block* block = first;
    while (block != null) {
        blocks[pos_to_index(block->pos)] = block;
        block = block->next;
    }
}

Cell* Cell::get_cell(UNIT* pos) {
//    printf ("Cell::get_cell:1: %d,%d,%d\n",pos[0],pos[1],pos[2]);
    BlockPos bp;
    if (!_normalise_position(&bp, pos)) {
        return null;
    }
//    printf ("Cell::get_cell:2: %d,%d,%d\n",pos[0],pos[1],pos[2]);
//     printf ("Cell::get_cell:2:cell: %d\n", bp->cell);
    Block* b = bp.cell->_get_block(bp.pos);
//    printf ("Cell::get_cell:3: %d,%d,%d - %d\n",pos[0],pos[1],pos[2],b);
    //delete bp;
//    printf ("Cell::get_cell:4: %d,%d,%d - %d\n",pos[0],pos[1],pos[2],b);
    if(b == null) {
//        printf ("Cell::get_cell:4a: %d,%d,%d - %d\n",pos[0],pos[1],pos[2]);
        return null; }
//    printf ("Cell::get_cell:5: %d,%d,%d - %d\n",pos[0],pos[1],pos[2],b->get_type());

    if (b != null && b->get_type() == BLOCK_TYPE_CELL) {
        return dynamic_cast<Cell*> (b);
    }
    return null;
}

Block* Cell::get_block(UNIT* pos) {

    BlockPos bp;
    if (!_normalise_position(&bp, pos)) {
        return null;
    }

    Block* b = bp.cell->_get_block(bp.pos);
    return b;
}

Block* Cell::_get_block(UNIT* pos) {
//    printf ("Cell::_get_block:0: %d,%d,%d - %d\n",pos[0],pos[1],pos[2],blocks);
    if (blocks != null) { //We have the block "hashmap" ready let's use that
        return blocks[pos_to_index(pos)];
    } else if( this->first != null){//we don't have a "hashmap" ready, go though the linked list.
        Block* block = this->first;
//        printf ("Cell::_get_block:1: %d,%d,%d - %d\n",pos[0],pos[1],pos[2],block);
        while (block != null) {
            if (block->is_at(pos))
                return block;
            block = block->next;
        }
//        printf ("Cell::_get_block:2: %d,%d,%d - %d\n",pos[0],pos[1],pos[2],block);
    }

    return null;
}

int Cell::pos_to_index(UNIT* pos) {
//    printf ("Cell::pos_to_index: %d,%d,%d - %d\n",pos[0],pos[1],pos[2],(pos[X] + pos[Y] * CELL_WIDTH + pos[Z] * CELL_WIDTH*CELL_HEIGHT));
    return pos[X] + pos[Y] * CELL_WIDTH + pos[Z] * CELL_WIDTH*CELL_HEIGHT;
}

void Block::get_neighbour_pos(UNIT ret[3], UNIT dir) {
    //UNIT* ret = new UNIT[3];
    ret[X] = this->pos[X] + WORLD_X[dir];
    ret[Y] = this->pos[Y] + WORLD_Y[dir];
    ret[Z] = this->pos[Z] + WORLD_Z[dir];
//    printf ("Cell::get_neighbour_pos:1: %d,%d,%d\n",ret[0],ret[1],ret[2]);
    //return ret;
}

Cell* Cell::get_neighbour(UNIT dir) {
//    printf ("Cell::get_neighbour: %d\n",this->parent);
    if (this->neighbours[dir] == 0) {
        Cell* c = new Cell(this->depth, this->parent);
        UNIT ret[3];
        get_neighbour_pos(ret, dir);
        this->parent->set_block(ret, c);
        return c;
    } else {
        return this->neighbours[dir];
    }
}

bool Cell::_normalise_position(BlockPos* bp, UNIT* pos) {

    //BlockPos* bp = new BlockPos;
    bp->cell = this;
    bp->pos[0] = pos[0];
    bp->pos[1] = pos[1];
    bp->pos[2] = pos[2];

    for (int i = 0; i < 3; i++) {
        while (bp->pos[i] < 0) {
            bp->pos[i] += CELL_WHD[i];
            bp->cell = bp->cell->get_neighbour(i * 2);
        }
        while (bp->pos[i] >= CELL_WHD[i]) {
            bp->pos[i] -= CELL_WHD[i];
            bp->cell = bp->cell->get_neighbour(i * 2 + 1);
        }
        if(bp->cell == null){
            return false;
        }
    }
    return true;
    //return bp;
}

void Cell::_register_child(UNIT* pos, Block* block) {
   
    count++;
    block->parent = this;
    
    // This code adds the block into the cell's linked list of blocks
    if (this->first == 0) {
        this->first = block;
        this->last = block;
    } else {
        block->prev = this->last;
        this->last->next = block;
        this->last = block;
    }
    
    if (count > CELL_CRITICAL_MASS) {
        _init_hash();
    }

    
    if (block->get_type() == BLOCK_TYPE_CELL) {
        Cell* cell = dynamic_cast<Cell*> (block);
        if (cell) {
            _register_cell(pos, cell);
        }
    }
}

void Cell::_register_cell(UNIT* pos, Cell* cell) {


    UNIT temp_pos[3] = {pos[0] + 1, pos[1], pos[2]};
    _register_cell_neighbour(temp_pos, cell, WORLD_XP, WORLD_XN);

    temp_pos[0] -= 2;
//     printf ("Cell::_register_cell: %d,%d,%d\n",temp_pos[0],temp_pos[1],temp_pos[2]);
    _register_cell_neighbour(temp_pos, cell, WORLD_XN, WORLD_XP);
    

    temp_pos[0] += 1;
    temp_pos[1] += 1;
    _register_cell_neighbour(temp_pos, cell, WORLD_YP, WORLD_YN);

    temp_pos[1] -= 2;
    _register_cell_neighbour(temp_pos, cell, WORLD_YN, WORLD_YP);

    temp_pos[1] += 1;
    temp_pos[2] += 1;
    _register_cell_neighbour(temp_pos, cell, WORLD_ZP, WORLD_ZN);

    temp_pos[2] -= 2;
    _register_cell_neighbour(temp_pos, cell, WORLD_ZN, WORLD_ZP);

    
}

void Cell::_register_cell_neighbour(UNIT* pos, Cell* cell, UNIT dir, UNIT rdir) {
//    printf ("Cell::_register_cell_neighbour:1: %d,%d,%d\n",pos[0],pos[1],pos[2]);
    Cell* c = get_cell(pos);
//    printf ("Cell::_register_cell_neighbour:2: %d,%d,%d - %d\n",pos[0],pos[1],pos[2],c);
    if (c != null) {
        c->neighbours[dir] = cell;
        cell->neighbours[rdir] = c;
    }
}

TerrainDefinition::TerrainDefinition(int id) {
    this->id = id;
}

TerrainDefinition::~TerrainDefinition() {
    delete renderer;
}